

#include "libEink_Spectra6_render.h"
#include "bmpView_JY.h"
#include "timeInterval_JY.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef enum _fileType
{
    BMP_fileType,
    PPM_fileType,
    PGM_fileType
} fileType;

static char *INPUT_IMAGE_PATH = NULL;
static fileType INPUT_IMAGE_TYPE;
static char *OUTPUT_IMAGE_PATH = NULL;
static fileType OUTPUT_IMAGE_TYPE;
static char *LUT_PATH = NULL;
static EinkDitheringMethod DITHER = EINK_STUCKI_SERPENTINE;
static int MODE = 1;
static int TFT_mode = 0;
static int HUE = 0;
static int SATURATION = 0;
static int BRIGHTNESS = 0;
static int CONTRAST = 0;
static double GAMMA = 1.0;

int processArguments(int argc, char *argv[])
{

    char usageMsg[] = "usage: E6_render [--version] [--help] [-i <path> | --input <path>] [-o <path> | --output <path>] [-l <path> | --lut <path>] [-d <path> | --dither <path>] [-m <number> | --mode <number>] [-HGD <num> | --HGD <num>] [-h <number> | --hue <number>] [-s <number> | --saturation <number>] [-b <number> | --brightness <number>] [-c <number> | --contrast <number>] [-g <number> | --gamma <number>]\n"
                      "\n"
                      "Options:\n"
                      "  --version                            Show version information\n"
                      "  --help                               Show this help message\n"
                      "  -i, --input <path>                   Input BMP file path (required)\n"
                      "  -o, --output <path>                  Output BMP file path (required)\n"
                      "  -l, --lut <path>                     Look-Up-Table path (required)\n"
                      "                                           - Spectra6_Render_LUT_Default_v1.bin (renamed from project_adaptive_LUT_V0.bin)\n"
                      "                                           - Spectra6_Render_LUT_Default_v2.bin\n"
                      "  -d <number>, --dither <number>       Choose dithering method:\n"
                      "                                           1: Floyd\n"
                      "                                           2: Floyd Serpentine\n"
                      "                                           3: Jarvis, Judice, and Ninkle\n"
                      "                                           4: Jarvis, Judice, and Ninkle Serpentine\n"
                      "                                           5: Stucki\n"
                      "                                  (default)6: Stucki Serpentine \n"
                      "  -m <number>, --mode <number>         Choose output format Mode:\n"
                      "                                  (default)1: Output will be in external T-con format \n"
                      "                                           2: Output will be in AOI IC format\n"
                      "                                           3: Output will be in Simulation format\n"
                      "  -t <number>, --TFT <number>          Set output for TFT format transfer:\n"
                      "                                  (default)0: Disabled \n"
                      "                                           1: Enable HGD with Source odd/even.\n"
                      "                                           2: Enable HGD with Source even/odd.\n"
                      "                                           3: Enable Swapping odd and even Gate.\n"
                      "                                           4: Enable HSD with Gate odd/even.\n"
                      "                                           5: Enable HSD with Gate even/odd.\n"
                      "  -h <number>, --hue <number>          Set hue (range: 0~360, default: 0)\n"
                      "  -s <number>, --saturation <number>   Set saturation (range: -100~100, default: 0)\n"
                      "  -b <number>, --brightness <number>   Set brightness (range: -100~100, default: 0)\n"
                      "  -c <number>, --contrast <number>     Set contrast (range: -100~100, default: 0)\n"
                      "  -g <number>, --gamma <number>        Set gamma (range: 0.0~2.0, default: 0.0)\n";

    if (argc == 1)
    {
        printf("%s", usageMsg);
        return -1;
    }

    // Check command line parameters
    for (int i = 1; i < argc; i++)
    {
        // help
        if (strcmp(argv[i], "--help") == 0)
        {
            // show the discription for useage
            printf("%s", usageMsg);
            return -1;
        }
        // version
        else if (strcmp(argv[i], "--version") == 0)
        {
            // show update log and version
            printf("%s", Eink_get_updateLog());
            return -1;
        }
        // input image path
        else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--input") == 0)
        {
            if (i + 1 < argc)
            {
                // const char* inputFile = argv[i + 1];
                INPUT_IMAGE_PATH = argv[i + 1];
                i++;                                                    // skip argument
                const char *extension = strrchr(INPUT_IMAGE_PATH, '.'); // get file extension

                if (extension && (strcmp(extension, ".bmp") == 0))
                {
                    INPUT_IMAGE_TYPE = BMP_fileType;
                }
                // else if (extension && (strcmp(extension, ".ppm") == 0))
                // {
                //     INPUT_IMAGE_TYPE = PPM_fileType;
                // }
                else
                {
                    printf("The input file format is not supported. Only BMP file formats are supported.\n");
                    return -1;
                }
            }
            else
            {
                printf("The input image path is missing.\n");
                return -1;
            }
        }
        // output image path
        else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0)
        {
            if (i + 1 < argc)
            {
                // const char* outputFile = argv[i + 1];
                OUTPUT_IMAGE_PATH = argv[i + 1];
                i++; // skip argument

                const char *extension = strrchr(OUTPUT_IMAGE_PATH, '.'); // get file extension
                if (extension && (strcmp(extension, ".bmp") == 0))
                {
                    OUTPUT_IMAGE_TYPE = BMP_fileType;
                }
                // else if (extension && (strcmp(extension, ".pgm") == 0))
                // {
                //     OUTPUT_IMAGE_TYPE = PGM_fileType;
                // }
                else
                {
                    printf("The output file format is not supported. Only BMP file formats are supported.\n");
                    return -1;
                }
            }
            else
            {
                printf("The output image path is missing.\n");
                return -1;
            }
        }
        // lut path
        else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--lut") == 0)
        {
            if (i + 1 < argc)
            {
                LUT_PATH = argv[i + 1];
                i++; // skip argument
            }
            else
            {
                printf("The LUT path is missing.\n");
                return -1;
            }
        }
        // dither
        else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--dither") == 0)
        {
            if (i + 1 < argc)
            {
                int num = atoi(argv[i + 1]);
                i++; // skip argument

                switch (num)
                {
                case 1:
                    DITHER = EINK_FLOYD;
                    break;
                case 2:
                    DITHER = EINK_FLOYD_SERPENTINE;
                    break;
                case 3:
                    DITHER = EINK_JARVIS_JUDICE_NINKLE;
                    break;
                case 4:
                    DITHER = EINK_JARVIS_JUDICE_NINKLE_SERPENTINE;
                    break;
                case 5:
                    DITHER = EINK_STUCKI;
                    break;
                case 6:
                    DITHER = EINK_STUCKI_SERPENTINE;
                    break;
                default:
                    DITHER = -1;
                    break;
                }
            }
            else
            {
                printf("The mode number is missing.\n");
                return -1;
            }
        }
        // mode
        else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mode") == 0)
        {
            if (i + 1 < argc)
            {
                MODE = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The mode number is missing.\n");
                return -1;
            }
        }
        // HGD
        else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--TFT") == 0)
        {
            if (i + 1 < argc)
            {
                TFT_mode = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The TFT number is missing.\n");
                return -1;
            }
        }
        // hue
        else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--hue") == 0)
        {
            if (i + 1 < argc)
            {
                HUE = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The hue number is missing.\n");
                return -1;
            }
        }
        // saturation
        else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--saturation") == 0)
        {
            if (i + 1 < argc)
            {
                SATURATION = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The saturation number is missing.\n");
                return -1;
            }
        }
        // brightness
        else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--brightness") == 0)
        {
            if (i + 1 < argc)
            {
                BRIGHTNESS = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The brightness number is missing.\n");
                return -1;
            }
        }
        // contrast
        else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--contrast") == 0)
        {
            if (i + 1 < argc)
            {
                CONTRAST = atoi(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The contrast number is missing.\n");
                return -1;
            }
        }
        // contrast
        else if (strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "--gamma") == 0)
        {
            if (i + 1 < argc)
            {
                GAMMA = atof(argv[i + 1]);
                i++; // skip argument
            }
            else
            {
                printf("The gamma number is missing.\n");
                return -1;
            }
        }
        // unkown option
        else
        {
            printf("unknown option: %s\n", argv[i]);
            printf("%s", usageMsg);
            return -1;
        }
    }

    if (INPUT_IMAGE_PATH == NULL)
        printf("Please provide the input file path by using the [-i <path> | --input <path>] flag\n");
    else if (OUTPUT_IMAGE_PATH == NULL)
        printf("Please provide the output file path by using the [-o <path> | --output <path>] flag.\n");
    else if (LUT_PATH == NULL)
        printf("Please provide the LUT path by using the [-l <path> | --lut <path>] flag.\n");
    else if (DITHER != EINK_FLOYD && DITHER != EINK_FLOYD_SERPENTINE && DITHER != EINK_JARVIS_JUDICE_NINKLE && DITHER != EINK_JARVIS_JUDICE_NINKLE_SERPENTINE && DITHER != EINK_STUCKI && DITHER != EINK_STUCKI_SERPENTINE)
        printf("The dither number only can be 1, 2, 3, 4, 5 or 6.\n");
    else if (MODE != 1 && MODE != 2 && MODE != 3)
        printf("The mode only can be 0 or 1.\n");
    else if (TFT_mode != 0 && TFT_mode != 1 && TFT_mode != 2 && TFT_mode != 3 && TFT_mode != 4 && TFT_mode != 5)
        printf("The TFT number only can be 0, 1, 2 or 3.\n");
    else if (HUE < 0 || HUE > 360)
        printf("The range of hue can only be between 0 and 360\n");
    else if (SATURATION < -100 || SATURATION > 100)
        printf("The range of saturation can only be between -100 and 100\n");
    else if (BRIGHTNESS < -100 || BRIGHTNESS > 100)
        printf("The range of brightness can only be between -100 and 100\n");
    else if (CONTRAST < -100 || CONTRAST > 100)
        printf("The range of contrast can only be between -100 and 100\n");
    else if (GAMMA < 0.0 || GAMMA > 2.0)
        printf("The range of gamma can only be between 0.0 and 2.0\n");
    else
        return 0;

    return -1;
}

int readBinaryFile_JY(const char *filename, uint8_t **fileBuffer, int *fileSize)
{
    *fileBuffer = (uint8_t *)nullptr;

    FILE *fp_s = NULL;
    fp_s = fopen(filename, "rb");

    if (fp_s == NULL)
    {
        printf("ERROR: Failed to open LUT file %s \n", filename);
        return -1;
    }
    // Check file size
    fseek(fp_s, 0, SEEK_END);
    *fileSize = ftell(fp_s);
    fseek(fp_s, 0, SEEK_SET);
    *fileBuffer = (uint8_t *)malloc(sizeof(uint8_t) * (*fileSize));
    fread(*fileBuffer, sizeof(uint8_t), *fileSize, fp_s);
    fclose(fp_s);

    return 0;
}

int main(int argc, char *argv[])
{
    printf("Spectra6 rendering library sample\n");

    EinkRenderErrMsg res;
    double timeCost;

    uint8_t *srcImg;
    uint8_t *dstImg;
    int width, height;
    uint8_t *lut;
    int lut_size;

    // ============ read args ============
    if (processArguments(argc, argv) != 0)
    {
        return -1;
    }

    // ============ init ============
    readBinaryFile_JY(LUT_PATH, &lut, &lut_size);

    countTimeInterval_JY();
    res = Eink_Spectra6_render_init(lut, lut_size);
    timeCost = countTimeInterval_JY();
    if (res == EINK_SUCCESS)
        printf("Eink_Spactra6_render_init cost: %lf ms\n", timeCost);

    // ============ read image ============
    readBMP_JY(&srcImg, &width, &height, INPUT_IMAGE_PATH, BGR, true);
    dstImg = (uint8_t *)malloc(width * height * 3 * sizeof(uint8_t));

    // ============ image adjustment ============
    if (HUE == 0 && SATURATION == 0 && BRIGHTNESS == 0.0 && CONTRAST == 0.0 && GAMMA == 1.0)
    {
        printf("Skip image adjustment.\n");
    }
    else
    {
        countTimeInterval_JY();
        res = Eink_image_adjustment(srcImg, EINK_BGR888, srcImg, EINK_BGR888, width, height, HUE, SATURATION, BRIGHTNESS, CONTRAST, GAMMA);
        timeCost = countTimeInterval_JY();
        if (res == EINK_SUCCESS)
            printf("Eink_image_adjustment cost: %lf ms\n", timeCost);
    }

    //============ Spectra6 render  ============
    countTimeInterval_JY();
    res = Eink_Spectra6_render(srcImg, EINK_BGR888, dstImg, EINK_BGR888, width, height, DITHER, MODE);
    timeCost = countTimeInterval_JY();
    if (res == EINK_SUCCESS)
        printf("Eink_Spactra6_render cost: %lf ms\n", timeCost);

    //============ TFT format transfer ============

    if (TFT_mode == 0)
    {
        printf("No TFT format transfer.\n");
    }
    else
    {
        countTimeInterval_JY();
        res = Eink_TFT_format_transfer(dstImg, &width, &height, TFT_mode);
        timeCost = countTimeInterval_JY();
        if (res == EINK_SUCCESS)
            printf("Eink_TFT_format_transfer cost: %lf ms\n", timeCost);
    }

    //============ save image  ============
    saveBMP_JY(dstImg, width, height, OUTPUT_IMAGE_PATH, BGR, true);

    return 0;
}